📑 Indice della Lezione
1. Introduzione alle Matrici
Una matrice è una struttura dati bidimensionale che organizza elementi in righe e colonne. In C, le matrici sono implementate come array di array.
- Matrice m×n: m righe e n colonne
- Elemento aij: elemento alla riga i e colonna j
- Matrice quadrata: m = n (stesso numero di righe e colonne)
- Diagonale principale: elementi dove i = j
- Matrice identità: diagonale = 1, resto = 0
Rappresentazione in Memoria
In C, le matrici sono memorizzate in modo row-major (per righe): gli elementi di ogni riga sono memorizzati consecutivamente in memoria.
// Matrice 3×3 int mat[3][3] = { {1, 2, 3}, {4, 5, 6}, {7, 8, 9} }; // In memoria: [1][2][3][4][5][6][7][8][9] // Indirizzo elemento mat[i][j] = base + (i * n_colonne + j) * sizeof(tipo)
2. Dichiarazione e Inizializzazione
2.1 Array Statici
#include <stdio.h> int main(void) { // Dichiarazione semplice int mat1[3][4]; // Matrice 3×4 non inizializzata // Inizializzazione completa int mat2[2][3] = { {1, 2, 3}, {4, 5, 6} }; // Inizializzazione parziale (il resto è 0) int mat3[3][3] = { {1, 2}, // {1, 2, 0} {3} // {3, 0, 0} // {0, 0, 0} }; // Inizializzazione senza specificare la prima dimensione int mat4[][3] = { {1, 2, 3}, {4, 5, 6} }; // Il compilatore deduce che è 2×3 // Inizializzazione a zero int mat_zero[3][3] = {0}; // Tutti gli elementi a 0 // Accesso agli elementi mat1[0][0] = 10; int valore = mat2[1][2]; // valore = 6 return 0; }
2.2 Matrici come Parametri di Funzione
#include <stdio.h> // Metodo 1: Dimensione fissa void stampa_matrice_fissa(int mat[3][4]) { for (int i = 0; i < 3; i++) { for (int j = 0; j < 4; j++) { printf("%d ", mat[i][j]); } printf("\n"); } } // Metodo 2: Numero colonne fisso, righe variabili void stampa_matrice_variabile(int mat[][4], int righe) { for (int i = 0; i < righe; i++) { for (int j = 0; j < 4; j++) { printf("%d ", mat[i][j]); } printf("\n"); } } // Metodo 3: Array di puntatori (più flessibile) void stampa_matrice_puntatori(int **mat, int righe, int colonne) { for (int i = 0; i < righe; i++) { for (int j = 0; j < colonne; j++) { printf("%d ", mat[i][j]); } printf("\n"); } } // Metodo 4: Puntatore singolo (trattare come array 1D) void stampa_matrice_flat(int *mat, int righe, int colonne) { for (int i = 0; i < righe; i++) { for (int j = 0; j < colonne; j++) { // Accesso: mat[i * colonne + j] printf("%d ", mat[i * colonne + j]); } printf("\n"); } }
3. Allocazione Dinamica
3.1 Metodo 1: Array Contiguo (Più Efficiente)
#include <stdio.h> #include <stdlib.h> /** * @brief Alloca una matrice come array contiguo * @return Puntatore alla matrice o NULL se errore */ int* crea_matrice_contigua(int righe, int colonne) { int *mat = (int*)malloc(righe * colonne * sizeof(int)); if (mat == NULL) { fprintf(stderr, "Errore di allocazione memoria\n"); return NULL; } // Inizializza a zero for (int i = 0; i < righe * colonne; i++) { mat[i] = 0; } return mat; } /** * @brief Accede a elemento mat[i][j] */ int get_elemento(int *mat, int i, int j, int colonne) { return mat[i * colonne + j]; } void set_elemento(int *mat, int i, int j, int colonne, int valore) { mat[i * colonne + j] = valore; } void libera_matrice_contigua(int *mat) { free(mat); }
3.2 Metodo 2: Array di Puntatori
/** * @brief Alloca matrice come array di puntatori * Ogni riga è un array separato */ int** crea_matrice_puntatori(int righe, int colonne) { // Alloca array di puntatori alle righe int **mat = (int**)malloc(righe * sizeof(int*)); if (mat == NULL) { return NULL; } // Alloca ogni riga for (int i = 0; i < righe; i++) { mat[i] = (int*)malloc(colonne * sizeof(int)); if (mat[i] == NULL) { // Libera memoria già allocata in caso di errore for (int j = 0; j < i; j++) { free(mat[j]); } free(mat); return NULL; } // Inizializza riga a zero for (int j = 0; j < colonne; j++) { mat[i][j] = 0; } } return mat; } void libera_matrice_puntatori(int **mat, int righe) { if (mat == NULL) return; for (int i = 0; i < righe; i++) { free(mat[i]); } free(mat); }
3.3 Metodo 3: Singola Allocazione con Puntatori (Ottimizzato)
/** * @brief Alloca matrice con singola malloc ma accesso tramite puntatori * Combina efficienza del metodo 1 con comodità del metodo 2 */ int** crea_matrice_ottimizzata(int righe, int colonne) { // Alloca tutto in un blocco unico int **mat = (int**)malloc(righe * sizeof(int*) + righe * colonne * sizeof(int)); if (mat == NULL) { return NULL; } // I dati iniziano dopo l'array di puntatori int *dati = (int*)(mat + righe); // Imposta i puntatori alle righe for (int i = 0; i < righe; i++) { mat[i] = dati + i * colonne; } // Inizializza a zero for (int i = 0; i < righe * colonne; i++) { dati[i] = 0; } return mat; } void libera_matrice_ottimizzata(int **mat) { free(mat); // Una singola free! }
| Metodo | Memoria Contigua | Sintassi mat[i][j] | Performance | Chiamate malloc |
|---|---|---|---|---|
| Array Contiguo | ✅ Sì | ❌ No | ⭐⭐⭐ | 1 |
| Array di Puntatori | ❌ No | ✅ Sì | ⭐⭐ | n+1 |
| Ottimizzato | ✅ Sì | ✅ Sì | ⭐⭐⭐ | 1 |
4. Operazioni di Base
4.1 Stampa e Input
#include <stdio.h> /** * @brief Stampa una matrice in formato leggibile */ void stampa_matrice(int **mat, int righe, int colonne) { printf("\n"); for (int i = 0; i < righe; i++) { for (int j = 0; j < colonne; j++) { printf("%6d ", mat[i][j]); } printf("\n"); } printf("\n"); } /** * @brief Legge matrice da input utente */ void leggi_matrice(int **mat, int righe, int colonne) { printf("Inserisci gli elementi della matrice %dx%d:\n", righe, colonne); for (int i = 0; i < righe; i++) { for (int j = 0; j < colonne; j++) { printf("Elemento [%d][%d]: ", i, j); scanf("%d", &mat[i][j]); } } } /** * @brief Inizializza matrice con valori casuali */ void riempi_casuale(int **mat, int righe, int colonne, int max) { for (int i = 0; i < righe; i++) { for (int j = 0; j < colonne; j++) { mat[i][j] = rand() % max; } } }
4.2 Somma e Sottrazione
/** * @brief Somma due matrici: C = A + B * @return true se successo, false se dimensioni incompatibili */ bool somma_matrici(int **A, int **B, int **C, int righe, int colonne) { if (A == NULL || B == NULL || C == NULL) { return false; } for (int i = 0; i < righe; i++) { for (int j = 0; j < colonne; j++) { C[i][j] = A[i][j] + B[i][j]; } } return true; } /** * @brief Sottrazione di matrici: C = A - B */ bool sottrai_matrici(int **A, int **B, int **C, int righe, int colonne) { if (A == NULL || B == NULL || C == NULL) { return false; } for (int i = 0; i < righe; i++) { for (int j = 0; j < colonne; j++) { C[i][j] = A[i][j] - B[i][j]; } } return true; } /** * @brief Moltiplicazione per scalare: B = k * A */ void moltiplica_scalare(int **A, int **B, int k, int righe, int colonne) { for (int i = 0; i < righe; i++) { for (int j = 0; j < colonne; j++) { B[i][j] = k * A[i][j]; } } }
4.3 Prodotto di Matrici
/** * @brief Prodotto di matrici: C = A × B * A è m×n, B è n×p, C è m×p * Complessità: O(m × n × p) */ bool moltiplica_matrici(int **A, int **B, int **C, int m, int n, int p) { if (A == NULL || B == NULL || C == NULL) { return false; } // Inizializza C a zero for (int i = 0; i < m; i++) { for (int j = 0; j < p; j++) { C[i][j] = 0; } } // Prodotto: C[i][j] = Σ(A[i][k] * B[k][j]) per k da 0 a n-1 for (int i = 0; i < m; i++) { for (int j = 0; j < p; j++) { for (int k = 0; k < n; k++) { C[i][j] += A[i][k] * B[k][j]; } } } return true; } /** * @brief Prodotto ottimizzato (cache-friendly) * Migliore località spaziale */ bool moltiplica_matrici_ottimizzato(int **A, int **B, int **C, int m, int n, int p) { // Inizializza C for (int i = 0; i < m; i++) { for (int j = 0; j < p; j++) { C[i][j] = 0; } } // Scambia ordine dei loop per migliorare cache hit for (int i = 0; i < m; i++) { for (int k = 0; k < n; k++) { int temp = A[i][k]; for (int j = 0; j < p; j++) { C[i][j] += temp * B[k][j]; } } } return true; }
4.4 Trasposizione
/** * @brief Traspone una matrice: B = A^T * Se A è m×n, B è n×m */ void trasponi_matrice(int **A, int **B, int righe, int colonne) { for (int i = 0; i < righe; i++) { for (int j = 0; j < colonne; j++) { B[j][i] = A[i][j]; } } } /** * @brief Traspone matrice quadrata in-place */ void trasponi_in_place(int **mat, int n) { for (int i = 0; i < n; i++) { for (int j = i + 1; j < n; j++) { // Scambia mat[i][j] con mat[j][i] int temp = mat[i][j]; mat[i][j] = mat[j][i]; mat[j][i] = temp; } } }
5. Operazioni Avanzate
5.1 Determinante (Algoritmo Ricorsivo)
/** * @brief Calcola il determinante di una matrice quadrata * Usa sviluppo di Laplace (ricorsivo) * Complessità: O(n!) */ int determinante(int **mat, int n) { // Caso base: matrice 1×1 if (n == 1) { return mat[0][0]; } // Caso base: matrice 2×2 if (n == 2) { return mat[0][0] * mat[1][1] - mat[0][1] * mat[1][0]; } int det = 0; // Alloca sottomatrice (n-1)×(n-1) int **submat = crea_matrice_puntatori(n - 1, n - 1); // Sviluppo lungo la prima riga for (int x = 0; x < n; x++) { // Crea sottomatrice escludendo riga 0 e colonna x int sub_i = 0; for (int i = 1; i < n; i++) { int sub_j = 0; for (int j = 0; j < n; j++) { if (j == x) continue; submat[sub_i][sub_j++] = mat[i][j]; } sub_i++; } // Formula: det = Σ((-1)^j * a[0][j] * det(submat)) int segno = (x % 2 == 0) ? 1 : -1; det += segno * mat[0][x] * determinante(submat, n - 1); } libera_matrice_puntatori(submat, n - 1); return det; } /** * @brief Calcola determinante con eliminazione di Gauss * Complessità: O(n³) - molto più efficiente! */ double determinante_gauss(double **mat, int n) { // Crea copia della matrice double **temp = (double**)malloc(n * sizeof(double*)); for (int i = 0; i < n; i++) { temp[i] = (double*)malloc(n * sizeof(double)); for (int j = 0; j < n; j++) { temp[i][j] = mat[i][j]; } } double det = 1.0; // Eliminazione gaussiana for (int i = 0; i < n; i++) { // Trova pivot int max_row = i; for (int k = i + 1; k < n; k++) { if (fabs(temp[k][i]) > fabs(temp[max_row][i])) { max_row = k; } } // Scambia righe se necessario if (max_row != i) { double *tmp = temp[i]; temp[i] = temp[max_row]; temp[max_row] = tmp; det = -det; // Cambio di segno } // Se pivot è zero, det = 0 if (fabs(temp[i][i]) < 1e-10) { det = 0; break; } // Elimina colonna sotto il pivot for (int k = i + 1; k < n; k++) { double factor = temp[k][i] / temp[i][i]; for (int j = i; j < n; j++) { temp[k][j] -= factor * temp[i][j]; } } det *= temp[i][i]; } // Libera memoria for (int i = 0; i < n; i++) { free(temp[i]); } free(temp); return det; }
5.2 Matrice Inversa (Gauss-Jordan)
#include <math.h> /** * @brief Calcola la matrice inversa usando Gauss-Jordan * @param mat Matrice da invertire (n×n) * @param inv Matrice inversa risultante (n×n) * @return true se la matrice è invertibile, false altrimenti */ bool matrice_inversa(double **mat, double **inv, int n) { // Crea matrice aumentata [mat | I] double **aug = (double**)malloc(n * sizeof(double*)); for (int i = 0; i < n; i++) { aug[i] = (double*)malloc(2 * n * sizeof(double)); for (int j = 0; j < n; j++) { aug[i][j] = mat[i][j]; aug[i][n + j] = (i == j) ? 1.0 : 0.0; // Matrice identità } } // Eliminazione di Gauss-Jordan for (int i = 0; i < n; i++) { // Trova pivot int max_row = i; for (int k = i + 1; k < n; k++) { if (fabs(aug[k][i]) > fabs(aug[max_row][i])) { max_row = k; } } // Scambia righe if (max_row != i) { double *tmp = aug[i]; aug[i] = aug[max_row]; aug[max_row] = tmp; } // Controlla se matrice è singolare if (fabs(aug[i][i]) < 1e-10) { // Libera memoria for (int k = 0; k < n; k++) free(aug[k]); free(aug); return false; } // Normalizza riga pivot double pivot = aug[i][i]; for (int j = 0; j < 2 * n; j++) { aug[i][j] /= pivot; } // Elimina colonna in tutte le altre righe for (int k = 0; k < n; k++) { if (k != i) { double factor = aug[k][i]; for (int j = 0; j < 2 * n; j++) { aug[k][j] -= factor * aug[i][j]; } } } } // Copia la parte destra (matrice inversa) in inv for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { inv[i][j] = aug[i][n + j]; } } // Libera memoria for (int i = 0; i < n; i++) free(aug[i]); free(aug); return true; }
6. Algoritmi Complessi
6.1 Decomposizione LU
/** * @brief Decomposizione LU: A = L × U * L è triangolare inferiore, U è triangolare superiore * Utile per risolvere sistemi lineari * Complessità: O(n³) */ bool decomposizione_LU(double **A, double **L, double **U, int n) { // Inizializza L e U for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { L[i][j] = (i == j) ? 1.0 : 0.0; U[i][j] = 0.0; } } // Algoritmo di Doolittle for (int i = 0; i < n; i++) { // Calcola riga i di U for (int j = i; j < n; j++) { double sum = 0.0; for (int k = 0; k < i; k++) { sum += L[i][k] * U[k][j]; } U[i][j] = A[i][j] - sum; } // Calcola colonna i di L for (int j = i + 1; j < n; j++) { if (fabs(U[i][i]) < 1e-10) { return false; // Matrice singolare } double sum = 0.0; for (int k = 0; k < i; k++) { sum += L[j][k] * U[k][i]; } L[j][i] = (A[j][i] - sum) / U[i][i]; } } return true; } /** * @brief Risolve sistema Ax = b usando decomposizione LU * Prima risolve Ly = b, poi Ux = y */ void risolvi_LU(double **L, double **U, double *b, double *x, int n) { double *y = (double*)malloc(n * sizeof(double)); // Forward substitution: Ly = b for (int i = 0; i < n; i++) { double sum = 0.0; for (int j = 0; j < i; j++) { sum += L[i][j] * y[j]; } y[i] = (b[i] - sum) / L[i][i]; } // Back substitution: Ux = y for (int i = n - 1; i >= 0; i--) { double sum = 0.0; for (int j = i + 1; j < n; j++) { sum += U[i][j] * x[j]; } x[i] = (y[i] - sum) / U[i][i]; } free(y); }
6.2 Algoritmo di Strassen (Moltiplicazione Veloce)
/** * @brief Moltiplicazione di Strassen * Complessità: O(n^2.807) invece di O(n³) * Funziona per matrici di dimensione potenza di 2 */ void somma_mat(int **A, int **B, int **C, int n) { for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { C[i][j] = A[i][j] + B[i][j]; } } } void sottrai_mat(int **A, int **B, int **C, int n) { for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { C[i][j] = A[i][j] - B[i][j]; } } } void strassen(int **A, int **B, int **C, int n) { // Caso base: matrice 1×1 if (n == 1) { C[0][0] = A[0][0] * B[0][0]; return; } // Per matrici piccole, usa algoritmo standard if (n <= 64) { moltiplica_matrici(A, B, C, n, n, n); return; } int new_size = n / 2; // Alloca sottomatrici int **A11 = crea_matrice_puntatori(new_size, new_size); int **A12 = crea_matrice_puntatori(new_size, new_size); int **A21 = crea_matrice_puntatori(new_size, new_size); int **A22 = crea_matrice_puntatori(new_size, new_size); int **B11 = crea_matrice_puntatori(new_size, new_size); int **B12 = crea_matrice_puntatori(new_size, new_size); int **B21 = crea_matrice_puntatori(new_size, new_size); int **B22 = crea_matrice_puntatori(new_size, new_size); // Matrici per i prodotti intermedi M1-M7 int **M1 = crea_matrice_puntatori(new_size, new_size); int **M2 = crea_matrice_puntatori(new_size, new_size); int **M3 = crea_matrice_puntatori(new_size, new_size); int **M4 = crea_matrice_puntatori(new_size, new_size); int **M5 = crea_matrice_puntatori(new_size, new_size); int **M6 = crea_matrice_puntatori(new_size, new_size); int **M7 = crea_matrice_puntatori(new_size, new_size); int **temp1 = crea_matrice_puntatori(new_size, new_size); int **temp2 = crea_matrice_puntatori(new_size, new_size); // Dividi matrici in 4 sottomatrici for (int i = 0; i < new_size; i++) { for (int j = 0; j < new_size; j++) { A11[i][j] = A[i][j]; A12[i][j] = A[i][j + new_size]; A21[i][j] = A[i + new_size][j]; A22[i][j] = A[i + new_size][j + new_size]; B11[i][j] = B[i][j]; B12[i][j] = B[i][j + new_size]; B21[i][j] = B[i + new_size][j]; B22[i][j] = B[i + new_size][j + new_size]; } } // Calcola i 7 prodotti di Strassen // M1 = (A11 + A22) × (B11 + B22) somma_mat(A11, A22, temp1, new_size); somma_mat(B11, B22, temp2, new_size); strassen(temp1, temp2, M1, new_size); // M2 = (A21 + A22) × B11 somma_mat(A21, A22, temp1, new_size); strassen(temp1, B11, M2, new_size); // M3 = A11 × (B12 - B22) sottrai_mat(B12, B22, temp1, new_size); strassen(A11, temp1, M3, new_size); // M4 = A22 × (B21 - B11) sottrai_mat(B21, B11, temp1, new_size); strassen(A22, temp1, M4, new_size); // M5 = (A11 + A12) × B22 somma_mat(A11, A12, temp1, new_size); strassen(temp1, B22, M5, new_size); // M6 = (A21 - A11) × (B11 + B12) sottrai_mat(A21, A11, temp1, new_size); somma_mat(B11, B12, temp2, new_size); strassen(temp1, temp2, M6, new_size); // M7 = (A12 - A22) × (B21 + B22) sottrai_mat(A12, A22, temp1, new_size); somma_mat(B21, B22, temp2, new_size); strassen(temp1, temp2, M7, new_size); // Combina i risultati // C11 = M1 + M4 - M5 + M7 // C12 = M3 + M5 // C21 = M2 + M4 // C22 = M1 - M2 + M3 + M6 for (int i = 0; i < new_size; i++) { for (int j = 0; j < new_size; j++) { C[i][j] = M1[i][j] + M4[i][j] - M5[i][j] + M7[i][j]; C[i][j + new_size] = M3[i][j] + M5[i][j]; C[i + new_size][j] = M2[i][j] + M4[i][j]; C[i + new_size][j + new_size] = M1[i][j] - M2[i][j] + M3[i][j] + M6[i][j]; } } // Libera memoria (codice di pulizia omesso per brevità) }
6.3 Autovalori e Autovettori (Power Iteration)
/** * @brief Trova l'autovalore dominante e il suo autovettore * Usa il metodo della potenza */ double power_iteration(double **A, int n, double *autovettore, int max_iter, double tol) { // Inizializza autovettore casuale double *v = (double*)malloc(n * sizeof(double)); double *v_new = (double*)malloc(n * sizeof(double)); for (int i = 0; i < n; i++) { v[i] = (double)rand() / RAND_MAX; } // Normalizza double norm = 0.0; for (int i = 0; i < n; i++) { norm += v[i] * v[i]; } norm = sqrt(norm); for (int i = 0; i < n; i++) { v[i] /= norm; } double autovalore = 0.0; // Iterazione for (int iter = 0; iter < max_iter; iter++) { // v_new = A × v for (int i = 0; i < n; i++) { v_new[i] = 0.0; for (int j = 0; j < n; j++) { v_new[i] += A[i][j] * v[j]; } } // Calcola autovalore (Rayleigh quotient) double num = 0.0, den = 0.0; for (int i = 0; i < n; i++) { num += v[i] * v_new[i]; den += v[i] * v[i]; } double nuovo_autovalore = num / den; // Controlla convergenza if (fabs(nuovo_autovalore - autovalore) < tol) { autovalore = nuovo_autovalore; break; } autovalore = nuovo_autovalore; // Normalizza v_new norm = 0.0; for (int i = 0; i < n; i++) { norm += v_new[i] * v_new[i]; } norm = sqrt(norm); for (int i = 0; i < n; i++) { v[i] = v_new[i] / norm; } } // Copia autovettore finale for (int i = 0; i < n; i++) { autovettore[i] = v[i]; } free(v); free(v_new); return autovalore; }
7. Matrici Sparse
Le matrici sparse contengono prevalentemente zeri. Memorizzarle in modo efficiente risparmia molta memoria.
7.1 Formato COO (Coordinate)
typedef struct { int riga; int colonna; double valore; } ElementoSparse; typedef struct { ElementoSparse *elementi; int num_elementi; int righe; int colonne; int capacita; } MatriceSparse; /** * @brief Crea matrice sparse */ MatriceSparse* crea_matrice_sparse(int righe, int colonne, int capacita) { MatriceSparse *mat = (MatriceSparse*)malloc(sizeof(MatriceSparse)); mat->elementi = (ElementoSparse*)malloc(capacita * sizeof(ElementoSparse)); mat->num_elementi = 0; mat->righe = righe; mat->colonne = colonne; mat->capacita = capacita; return mat; } /** * @brief Inserisce elemento nella matrice sparse */ void inserisci_sparse(MatriceSparse *mat, int i, int j, double valore) { if (valore == 0.0) return; // Non memorizzare zeri if (mat->num_elementi >= mat->capacita) { // Espandi capacità mat->capacita *= 2; mat->elementi = (ElementoSparse*)realloc(mat->elementi, mat->capacita * sizeof(ElementoSparse)); } mat->elementi[mat->num_elementi].riga = i; mat->elementi[mat->num_elementi].colonna = j; mat->elementi[mat->num_elementi].valore = valore; mat->num_elementi++; } /** * @brief Ottiene elemento dalla matrice sparse */ double get_sparse(MatriceSparse *mat, int i, int j) { for (int k = 0; k < mat->num_elementi; k++) { if (mat->elementi[k].riga == i && mat->elementi[k].colonna == j) { return mat->elementi[k].valore; } } return 0.0; // Elemento non trovato = zero } /** * @brief Prodotto matrice sparse per vettore * y = A × x */ void moltiplica_sparse_vettore(MatriceSparse *A, double *x, double *y) { // Inizializza y a zero for (int i = 0; i < A->righe; i++) { y[i] = 0.0; } // Moltiplica solo elementi non-zero for (int k = 0; k < A->num_elementi; k++) { int i = A->elementi[k].riga; int j = A->elementi[k].colonna; double val = A->elementi[k].valore; y[i] += val * x[j]; } }
8. Ottimizzazione e Performance
8.1 Loop Tiling (Blocchi per Cache)
/** * @brief Prodotto matrici con loop tiling per migliorare cache hit * Divide matrici in blocchi che si adattano alla cache */ void moltiplica_tiled(double **A, double **B, double **C, int n, int block_size) { // Inizializza C for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { C[i][j] = 0.0; } } // Loop sui blocchi for (int ii = 0; ii < n; ii += block_size) { for (int jj = 0; jj < n; jj += block_size) { for (int kk = 0; kk < n; kk += block_size) { // Loop all'interno del blocco for (int i = ii; i < ii + block_size && i < n; i++) { for (int k = kk; k < kk + block_size && k < n; k++) { double temp = A[i][k]; for (int j = jj; j < jj + block_size && j < n; j++) { C[i][j] += temp * B[k][j]; } } } } } } }
8.2 Trasposizione Ottimizzata
/** * @brief Trasposizione ottimizzata con blocchi */ void trasponi_ottimizzata(double **src, double **dst, int n, int block_size) { for (int ii = 0; ii < n; ii += block_size) { for (int jj = 0; jj < n; jj += block_size) { for (int i = ii; i < ii + block_size && i < n; i++) { for (int j = jj; j < jj + block_size && j < n; j++) { dst[j][i] = src[i][j]; } } } } }
9. Esercizi Progressivi
💪 Esercizio 1: Matrice Identità FACILE
Obiettivo: Crea una funzione che genera una matrice identità di dimensione n×n.
Input: Dimensione n
Output: Matrice identità (1 sulla diagonale, 0 altrove)
Soluzione:
void crea_identita(int **mat, int n) { for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { mat[i][j] = (i == j) ? 1 : 0; } } }
💪 Esercizio 2: Rotazione 90° FACILE
Obiettivo: Ruota una matrice quadrata di 90° in senso orario.
Suggerimento: Prima trasponi, poi inverti ogni riga.
Soluzione:
void ruota_90_orario(int **mat, int n) { // Trasponi trasponi_in_place(mat, n); // Inverti ogni riga for (int i = 0; i < n; i++) { for (int j = 0; j < n / 2; j++) { int temp = mat[i][j]; mat[i][j] = mat[i][n - 1 - j]; mat[i][n - 1 - j] = temp; } } }
💪 Esercizio 3: Spirale MEDIO
Obiettivo: Riempi una matrice n×n con numeri da 1 a n² in senso spirale (orario dall'esterno).
Soluzione:
void riempi_spirale(int **mat, int n) { int top = 0, bottom = n - 1; int left = 0, right = n - 1; int num = 1; while (top <= bottom && left <= right) { // Destra for (int j = left; j <= right; j++) { mat[top][j] = num++; } top++; // Giù for (int i = top; i <= bottom; i++) { mat[i][right] = num++; } right--; // Sinistra if (top <= bottom) { for (int j = right; j >= left; j--) { mat[bottom][j] = num++; } bottom--; } // Su if (left <= right) { for (int i = bottom; i >= top; i--) { mat[i][left] = num++; } left++; } } }
💪 Esercizio 4: Traccia del Prodotto MEDIO
Obiettivo: Calcola la traccia di A×B senza calcolare l'intera matrice prodotto.
Formula: Tr(A×B) = Σi,j A[i][j] × B[j][i]
Soluzione:
double traccia_prodotto(double **A, double **B, int n) { double traccia = 0.0; for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { traccia += A[i][j] * B[j][i]; } } return traccia; }
💪 Esercizio 5: Sottomatrice con Somma Massima DIFFICILE
Obiettivo: Trova la sottomatrice rettangolare con somma massima.
Algoritmo: Usa Kadane 2D - complessità O(n³)
Soluzione:
// Kadane 1D per trovare subarray con somma massima int kadane_1d(int *arr, int n, int *start, int *end) { int max_sum = arr[0]; int current_sum = arr[0]; *start = 0; *end = 0; int temp_start = 0; for (int i = 1; i < n; i++) { if (current_sum < 0) { current_sum = arr[i]; temp_start = i; } else { current_sum += arr[i]; } if (current_sum > max_sum) { max_sum = current_sum; *start = temp_start; *end = i; } } return max_sum; } // Kadane 2D int sottomatrice_massima(int **mat, int m, int n, int *top, int *left, int *bottom, int *right) { int max_sum = INT_MIN; int *temp = (int*)malloc(m * sizeof(int)); // Prova tutte le coppie di colonne for (int col_left = 0; col_left < n; col_left++) { // Inizializza temp for (int i = 0; i < m; i++) { temp[i] = 0; } for (int col_right = col_left; col_right < n; col_right++) { // Aggiungi colonna corrente a temp for (int i = 0; i < m; i++) { temp[i] += mat[i][col_right]; } // Trova max subarray in temp int start, end; int sum = kadane_1d(temp, m, &start, &end); if (sum > max_sum) { max_sum = sum; *top = start; *bottom = end; *left = col_left; *right = col_right; } } } free(temp); return max_sum; }
💪 Esercizio 6: Rango di una Matrice DIFFICILE
Obiettivo: Calcola il rango di una matrice usando eliminazione gaussiana.
Soluzione:
int calcola_rango(double **mat, int m, int n) { // Crea copia della matrice double **temp = (double**)malloc(m * sizeof(double*)); for (int i = 0; i < m; i++) { temp[i] = (double*)malloc(n * sizeof(double)); for (int j = 0; j < n; j++) { temp[i][j] = mat[i][j]; } } int rango = 0; double epsilon = 1e-10; for (int col = 0; col < n && rango < m; col++) { // Trova pivot int pivot_row = -1; for (int i = rango; i < m; i++) { if (fabs(temp[i][col]) > epsilon) { pivot_row = i; break; } } // Se nessun pivot, passa alla colonna successiva if (pivot_row == -1) continue; // Scambia righe if (pivot_row != rango) { double *tmp = temp[rango]; temp[rango] = temp[pivot_row]; temp[pivot_row] = tmp; } // Elimina colonna sotto il pivot for (int i = rango + 1; i < m; i++) { double factor = temp[i][col] / temp[rango][col]; for (int j = col; j < n; j++) { temp[i][j] -= factor * temp[rango][j]; } } rango++; } // Libera memoria for (int i = 0; i < m; i++) free(temp[i]); free(temp); return rango; }
💪 Esercizio 7: Ricerca Percorso in Labirinto DIFFICILE
Obiettivo: Data una matrice che rappresenta un labirinto (0=libero, 1=muro), trova il percorso più breve da (0,0) a (n-1,n-1).
Algoritmo: BFS (Breadth-First Search)
Soluzione:
#include <stdbool.h> typedef struct { int x, y; int dist; } Punto; int trova_percorso_labirinto(int **labirinto, int n) { if (labirinto[0][0] == 1 || labirinto[n-1][n-1] == 1) { return -1; // Start o fine bloccati } // Direzioni: su, giù, sinistra, destra int dx[] = {-1, 1, 0, 0}; int dy[] = {0, 0, -1, 1}; // Matrice visitati bool **visitato = (bool**)malloc(n * sizeof(bool*)); for (int i = 0; i < n; i++) { visitato[i] = (bool*)calloc(n, sizeof(bool)); } // Coda per BFS (implementazione semplificata) Punto *queue = (Punto*)malloc(n * n * sizeof(Punto)); int front = 0, rear = 0; // Aggiungi punto di partenza queue[rear++] = (Punto){0, 0, 0}; visitato[0][0] = true; while (front < rear) { Punto curr = queue[front++]; // Raggiunto destinazione? if (curr.x == n-1 && curr.y == n-1) { int dist = curr.dist; // Libera memoria for (int i = 0; i < n; i++) free(visitato[i]); free(visitato); free(queue); return dist; } // Esplora vicini for (int i = 0; i < 4; i++) { int nx = curr.x + dx[i]; int ny = curr.y + dy[i]; // Controlla bounds e validità if (nx >= 0 && nx < n && ny >= 0 && ny < n && !visitato[nx][ny] && labirinto[nx][ny] == 0) { visitato[nx][ny] = true; queue[rear++] = (Punto){nx, ny, curr.dist + 1}; } } } // Nessun percorso trovato for (int i = 0; i < n; i++) free(visitato[i]); free(visitato); free(queue); return -1; }
💪 Esercizio 8: Moltiplicazione di Catena di Matrici ESPERTO
Obiettivo: Date n matrici, trova l'ordine ottimale di moltiplicazione che minimizza il numero di operazioni.
Algoritmo: Programmazione dinamica - O(n³)
Esempio: A(10×30) × B(30×5) × C(5×60)
(A×B)×C = 10×30×5 + 10×5×60 = 4500
A×(B×C) = 30×5×60 + 10×30×60 = 27000
Soluzione:
/** * @brief Trova costo minimo per moltiplicare catena di matrici * @param dims Array di dimensioni: mat[i] ha dimensioni dims[i]×dims[i+1] * @param n Numero di matrici * @return Numero minimo di moltiplicazioni scalari */ int catena_matrici_dp(int *dims, int n) { // m[i][j] = costo minimo per moltiplicare matrici da i a j int **m = (int**)malloc(n * sizeof(int*)); for (int i = 0; i < n; i++) { m[i] = (int*)calloc(n, sizeof(int)); } // Lunghezza catena for (int len = 2; len <= n; len++) { // Punto iniziale for (int i = 0; i < n - len + 1; i++) { int j = i + len - 1; m[i][j] = INT_MAX; // Prova tutti i punti di split for (int k = i; k < j; k++) { // Costo = costo(i,k) + costo(k+1,j) + costo moltiplicazione finale int costo = m[i][k] + m[k+1][j] + dims[i] * dims[k+1] * dims[j+1]; if (costo < m[i][j]) { m[i][j] = costo; } } } } int risultato = m[0][n-1]; // Libera memoria for (int i = 0; i < n; i++) free(m[i]); free(m); return risultato; } // Test int main(void) { // Esempio: 3 matrici con dimensioni 10×30, 30×5, 5×60 int dims[] = {10, 30, 5, 60}; int n = 3; // Numero di matrici int min_ops = catena_matrici_dp(dims, n); printf("Minimo numero di operazioni: %d\n", min_ops); return 0; }
💪 Esercizio 9: Sudoku Solver ESPERTO
Obiettivo: Risolvi un Sudoku (matrice 9×9) usando backtracking.
Soluzione:
#define N 9 // Controlla se è sicuro mettere num in mat[row][col] bool is_safe(int mat[N][N], int row, int col, int num) { // Controlla riga for (int x = 0; x < N; x++) { if (mat[row][x] == num) return false; } // Controlla colonna for (int x = 0; x < N; x++) { if (mat[x][col] == num) return false; } // Controlla box 3×3 int start_row = row - row % 3; int start_col = col - col % 3; for (int i = 0; i < 3; i++) { for (int j = 0; j < 3; j++) { if (mat[i + start_row][j + start_col] == num) { return false; } } } return true; } // Risolve Sudoku usando backtracking bool risolvi_sudoku(int mat[N][N]) { int row, col; // Trova prossima cella vuota bool found = false; for (row = 0; row < N; row++) { for (col = 0; col < N; col++) { if (mat[row][col] == 0) { found = true; break; } } if (found) break; } // Se non ci sono celle vuote, Sudoku risolto if (!found) return true; // Prova numeri da 1 a 9 for (int num = 1; num <= 9; num++) { if (is_safe(mat, row, col, num)) { mat[row][col] = num; // Ricorsione if (risolvi_sudoku(mat)) { return true; } // Backtrack mat[row][col] = 0; } } return false; // Trigger backtracking }
🎓 Suggerimenti per Padroneggiare le Matrici
- Pratica l'allocazione dinamica: Essenziale per matrici di dimensione variabile
- Ottimizza l'accesso: Sfrutta la località di memoria (row-major order)
- Usa algoritmi efficienti: O(n²⁸) di Strassen vs O(n³) standard
- Gestisci errori: Controlla sempre allocazioni e dimensioni
- Profila il codice: Usa Valgrind e gprof per trovare bottleneck
- Studia algoritmi avanzati: SVD, QR decomposition, metodi iterativi
- Pratica con LeetCode/HackerRank: Problemi su matrici sono comuni
📚 Risorse per Approfondire
- Libri: "Introduction to Algorithms" (CLRS), "Numerical Linear Algebra" (Trefethen)
- Librerie: BLAS, LAPACK, Eigen (C++), NumPy (Python)
- Online: MIT OpenCourseWare (Linear Algebra), Khan Academy
- Performance: Intel MKL, OpenBLAS per calcoli ottimizzati